package com.xt.base.dao.hibernate;

import java.io.Serializable;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import com.xt.base.dao.hibernate.code.CodeGenerator;
import com.xt.base.dao.hibernate.code.IAutoCreateCode;
import com.xt.base.reflection.BaseReflector;

public final class WritenableSessionImpl extends ReadOnlySessionImpl{

	private static final long serialVersionUID = -6360828860236993805L;
	private TempObjectContainer container;
	private boolean flushing;
	
	private class TempObjectContainer{
		private List<Object> delete;
		private List<Object> save;
		private List<Object> update;
		private List<Object> saveOrUpdate;
		private Map<String,Object> deleteMap;
		private Map<String,Object> saveMap;
		private Map<String,Object> updateMap;
		private Map<String,Object> saveOrUpdateMap;
		private boolean[] has = new boolean[]{false,false,false,false,false,false,false,false};
		
		private TempObjectContainer(){}
		
		private void addDelete(Object o){
			if(delete == null)delete = new ArrayList<Object>();
			delete.add(o);
			has[0] = has[0] || true;
		}
		private void addSave(Object o){
			if(save == null)save = new ArrayList<Object>();
			save.add(o);
			has[1] = has[1] || true;
		}
		private void addUpdate(Object o){
			if(update == null)update = new ArrayList<Object>();
			update.add(o);
			has[2] = has[2] || true;
		}
		private void addSaveOrUpdate(Object o){
			if(saveOrUpdate == null)saveOrUpdate = new ArrayList<Object>();
			saveOrUpdate.add(o);
			has[3] = has[3] || true;
		}
		private void addDelete(String key,Object o){
			if(deleteMap == null)deleteMap = new HashMap<String,Object>();
			deleteMap.put(key,o);
			has[4] = has[4] || true;
		}
		private void addSave(String key,Object o){
			if(saveMap == null)saveMap = new HashMap<String,Object>();
			saveMap.put(key,o);
			has[5] = has[5] || true;
		}
		private void addUpdate(String key,Object o){
			if(updateMap == null)updateMap = new HashMap<String,Object>();
			updateMap.put(key,o);
			has[6] = has[6] || true;
		}
		private void addSaveOrUpdate(String key,Object o){
			if(saveOrUpdateMap == null)saveOrUpdateMap = new HashMap<String,Object>();
			saveOrUpdateMap.put(key,o);
			has[7] = has[7] || true;
		}
		private void handleDelete(Session s){
			if(has[0]){
				for(Iterator itor = delete.iterator();
					itor.hasNext();)s.delete(itor.next());
				delete.clear();
				has[0] = false;
			}
			if(has[4]){
				for(Iterator itor = deleteMap.keySet().iterator();
					itor.hasNext();){
					String key = (String)itor.next();
					s.delete(key, deleteMap.get(key));
				}
				deleteMap.clear();
				has[4] = false;
			}
		}
		private void handleSave(Session s){
			if(has[1]){
				for(Iterator itor = save.iterator();
					itor.hasNext();)s.save(itor.next());
				save.clear();
				has[1] = false;
			}
			if(has[5]){
				for(Iterator itor = saveMap.keySet().iterator();
					itor.hasNext();){
					String key = (String)itor.next();
					s.save(key, saveMap.get(key));
				}
				saveMap.clear();
				has[5] = false;
			}
		}
		private void handleUpdate(Session s){
			if(has[2]){
				for(Iterator itor = update.iterator();
					itor.hasNext();)s.update(itor.next());
				update.clear();
				has[2] = false;
			}
			if(has[6]){
				for(Iterator itor = updateMap.keySet().iterator();
					itor.hasNext();){
					String key = (String)itor.next();
					s.update(key, updateMap.get(key));
				}
				updateMap.clear();
				has[6] = false;
			}
		}
		private void handleSaveOrUpdate(Session s){
			if(has[3]){
				for(Iterator itor = saveOrUpdate.iterator();
					itor.hasNext();)s.saveOrUpdate(itor.next());
				saveOrUpdate.clear();
				has[3] = false;
			}
			if(has[7]){
				for(Iterator itor = saveOrUpdateMap.keySet().iterator();
					itor.hasNext();){
					String key = (String)itor.next();
					s.saveOrUpdate(key, saveOrUpdateMap.get(key));
				}
				saveOrUpdateMap.clear();
				has[7] = false;
			}
		}
		private void clear(){
			if(delete != null)delete.clear();
			if(save != null)save.clear();
			if(update != null)update.clear();
			if(saveOrUpdate != null)saveOrUpdate.clear();
			if(deleteMap != null)deleteMap.clear();
			if(saveMap != null)saveMap.clear();
			if(updateMap != null)updateMap.clear();
			if(saveOrUpdateMap != null)saveOrUpdateMap.clear();
		}
		private void handleTempObjects(Session s){
			handleDelete(s);
			handleSave(s);
			handleUpdate(s);
			handleSaveOrUpdate(s);
		}
		private boolean hasEntity(){
			return has[0] || has[1] || has[2] || has[3] || has[4] || has[5] || has[6] || has[7];
		}
	}
	
	public WritenableSessionImpl(Session session){
		super(session);
		container = new TempObjectContainer();
		flushing = false;
	}
	
	public boolean isDirty(){
		return !container.hasEntity() && session.isDirty();
	}
	
	public void clear(){
		container.clear();
		session.clear();
	}

	public Connection close() throws HibernateException {
		container.clear();
		if(session.isOpen()){
			return session.close();
		} 
		
		return null ;
		
	}
	
	public Query createFilter(Object arg0, String arg1) throws HibernateException {
		return new ReadOnlyQueryImpl(session.createFilter(arg0, arg1));
	}

	public Query createQuery(String arg0) throws HibernateException {
		return session.createQuery(arg0);
	}

	public SQLQuery createSQLQuery(String arg0) throws HibernateException {
		return session.createSQLQuery(arg0);
	}

	public void delete(Object arg0) throws HibernateException{
		if(flushing)container.addDelete(arg0);
		else session.delete(arg0);
	}

	public void delete(String arg0, Object arg1) throws HibernateException {
		if(flushing)container.addDelete(arg0,arg1);
		else session.delete(arg0,arg1);
	}

	public void flush() throws HibernateException {
		if(!flushing){
			flushing = true;
			try{
				session.flush();
				while(container.hasEntity()){
					container.handleTempObjects(session);
					session.flush();
				}
			}finally{
				if(container.hasEntity())container.clear();
				flushing = false;
			}
		}else log.warn("flushing. so do nothing.");
	}

	public Query getNamedQuery(String arg0) throws HibernateException {
		return session.getNamedQuery(arg0);
	}

	public Session getSession(EntityMode arg0) {
		return new WritenableSessionImpl(session.getSession(arg0));
	}

	public SessionFactory getSessionFactory() {
		return null;
	}

	public Serializable save(Object arg0) throws HibernateException {
		if(arg0 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg0,session);
		if(flushing){
			container.addSave(arg0);
			try{
				return (String)BaseReflector.getValueIgnoreAccessable(arg0, "code");
			}catch(Exception e){
				return null;
			}
		}else 	return session.save(arg0);
	}

	public Serializable save(String arg0, Object arg1) throws HibernateException {
		if(arg1 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg1,session);
		if(flushing){
			container.addSave(arg0,arg1);
			try{
				return (String)BaseReflector.getValueIgnoreAccessable(arg1, "code");
			}catch(Exception e){
				return null;
			}
		}else 	return session.save(arg0,arg1);
	}

	public void saveOrUpdate(Object arg0) throws HibernateException {
		if(arg0 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg0,session);
		if(flushing)container.addSaveOrUpdate(arg0);
		else session.saveOrUpdate(arg0);
	}

	public void saveOrUpdate(String arg0, Object arg1) throws HibernateException {
		if(arg1 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg1,session);
		if(flushing)container.addSaveOrUpdate(arg0,arg1);
		else session.saveOrUpdate(arg0,arg1);
	}

	public void setReadOnly(Object arg0, boolean arg1) {
		session.setReadOnly(arg0, arg1);
	}

	public void update(Object arg0) throws HibernateException {
		if(arg0 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg0,session);
		if(flushing)container.addUpdate(arg0);
		else session.update(arg0);
	}

	public void update(String arg0, Object arg1) throws HibernateException {
		if(arg1 instanceof IAutoCreateCode)
			CodeGenerator.setCodes((IAutoCreateCode)arg1,session);
		if(flushing)container.addUpdate(arg0,arg1);
		else session.update(arg0,arg1);
	}
	
	private static final org.apache.log4j.Logger 
		log = org.apache.log4j.Logger.getLogger(WritenableSessionImpl.class);
}
